/*** xrkmonitor license ***

   Copyright (c) 2019 by rockdeng

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.


   字符云监控(xrkmonitor) 开源版 (c) 2019 by rockdeng
   当前版本：v1.0
   使用授权协议： apache license 2.0

   云版本主页：http://xrkmonitor.com

   云版本为开源版提供永久免费告警通道支持，告警通道支持短信、邮件、
   微信等多种方式，欢迎使用

   监控插件 linux_base 功能:
   		使用监控系统 api 通过读取 proc 文件系统实现 linux 基础信息上报, 包括 cpu/内存/磁盘/网络/进程
            cpu ：使用率(系统、用户、软中断、其它)、平均负载、cpu 切换次数等
            内存：使用率、可用内存、swap使用率等
            磁盘：使用率、磁盘IO
            网络：流量、包量、tcp 连接数、udp 包量、网络IO等

****/

#include <stdlib.h>
#include <inttypes.h>
#include <errno.h>
#include <unistd.h>
#include <string>
#include <stdio.h>
#include <math.h>
#include <mt_report.h>
#include <string.h>
#include <limits.h>
#include <signal.h>
#include <iostream>
#include <map>
#include "xrk_linux_base.h"
#include "net.h"
#include "cpu.h"
#include "disk.h"
#include "mem.h"

int g_aryPluginAttr[XRK_PLUGIN_ATTRS_COUNT_MAX];
const std::string g_strPluginName(XRK_PLUGIN_NAME);

// 插件实时表格统计时间
int g_iTableBaseInfoStaticTime = 0;
int g_iTableDiskStatAllStaticTime = 0;
int g_iTableNetDevAllStaticTime = 0;
int g_iTableNetTuPackStatStaticTime = 0;
int g_iTableCpuAllStatStaticTime = 0;
int g_iTableMemStatStaticTime = 0;

TcpuUse g_stCpuUse = {0};
TTcpUdpInfo g_stNetTcpUdpInfo = {0};
bool g_bNotSupportTcpUdp = false;
TNetUseInfo g_stNetUse = {0};
TDiskStatInfo * g_pDiskStatAry = NULL;
TDiskStatInfo * g_pDiskStatAryForTable = NULL;
std::map<std::string, TDiskInfo> g_stDiskInfo;
char g_szCpuInfo[32] = "null";

TMemInfo g_stMemInfo = {0};
TMemUse g_stMemUse = {0};

uint32_t g_dwCurTimeSec = 0;
int g_iReadInfoPerTimeSec = 0;
int g_iReportInfoPerTimeSec = 0;
int g_iNotMonitorNetLo = 0;
int g_iMaxDiskStatCount = 0;
int g_iDiskStatCount = 0;

int g_iDiskReadTimesWarnCfg = 0;
int g_iDiskWriteTimesWarnCfg = 0;
int g_iReadSectorsWarnCfg = 0;
int g_iWriteSectorsWarnCfg = 0;
int g_iIoUseTimeMsWarnCfg = 0;

int g_iLocalUdpPackErrorWarnLog = 0;
int g_iLocalTcpPackErrorWarnLog = 0;
int g_iRemoteTcpPackErrorWarnLog = 0;
int g_iRemoteUdpPackErrorWarnLog = 0;

bool s_exit = false;
TBaseInfo g_stBaseInfo;

const char * GetCmdResult(const char *cmd)
{
    static char s_buf[4096] = {0};
    s_buf[0] = '\0';
    FILE *fp = popen(cmd, "r");
    if(!fp) {
        MtReport_Log_Error("popen failed, cmd:%s, msg:%s", cmd, strerror(errno));
        return NULL;
    }

    int len = fread(s_buf, 1, sizeof(s_buf)-1, fp);
    if(len >= (int)sizeof(s_buf) || ferror(fp)) {
        MtReport_Log_Error("fread failed ret:%d, msg:%s", len, strerror(errno));
        pclose(fp);
        return NULL;
    }
    pclose(fp);
    s_buf[len] = '\0';
    return s_buf;
}

inline int MemUnitTokB()
{
    if(!strcmp(g_stMemInfo.szUnit, "kB"))
        return 1;
    if(!strcmp(g_stMemInfo.szUnit, "mB"))
        return 1000;
    if(!strcmp(g_stMemInfo.szUnit, "gB"))
        return 1000000;
    return 1;
}

static void ReadMemUse()
{
    memset(&g_stMemInfo, 0, sizeof(g_stMemInfo));
    if(GetMemInfo(g_stMemInfo) < 0)
        return;

    uint32_t dwFree = g_stMemInfo.dwMemFree + g_stMemInfo.dwCached - g_stMemInfo.dwDirty - g_stMemInfo.dwMapped;
    int32_t iUsePer = (int)((g_stMemInfo.dwMemTotal-dwFree)*100/g_stMemInfo.dwMemTotal);
    g_stMemUse.iMemUse += iUsePer;

    g_stBaseInfo.dwMemSpace = g_stMemInfo.dwMemTotal;
    g_stBaseInfo.iMemUsePercent = iUsePer;

    int32_t iSwapUse = 0;
    if(g_stMemInfo.dwSwapTotal > 0) {
        iSwapUse = (int)((g_stMemInfo.dwSwapTotal-g_stMemInfo.dwSwapFree)*100/g_stMemInfo.dwSwapTotal);
         g_stMemUse.iSwapUse += iSwapUse;
    }
    g_stMemUse.bStaticTimes++;
    g_stMemUse.dwAvailable = g_stMemInfo.dwMemAvailable*MemUnitTokB();

    MtReport_Log_Debug("get meminfo - unit:%s total:%u free:%u buffers:%u cached:%u dirty:%u maped:%u, use:%u%%, avail:%u",
        g_stMemInfo.szUnit, g_stMemInfo.dwMemTotal,
        g_stMemInfo.dwMemFree, g_stMemInfo.dwBuffers, g_stMemInfo.dwCached, g_stMemInfo.dwDirty, 
        g_stMemInfo.dwMapped, iUsePer, g_stMemUse.dwAvailable);
}

static void ReportCpuUse(int iCpu, int iUse)
{
	switch(iCpu) {
		case 0:
			MtReport_Attr_Set(XRK_ATTR_CPU_TOAL_USE, iUse);
			break;
		case 1:
			MtReport_Attr_Set(XRK_ATTR_CPU0_USE, iUse);
			break;
		case 2:
			MtReport_Attr_Set(XRK_ATTR_CPU1_USE, iUse);
			break;
		case 3:
			MtReport_Attr_Set(XRK_ATTR_CPU2_USE, iUse);
			break;
		case 4:
			MtReport_Attr_Set(XRK_ATTR_CPU3_USE, iUse);
			break;
		case 5:
			MtReport_Attr_Set(XRK_ATTR_CPU4_USE, iUse);
			break;
		case 6:
			MtReport_Attr_Set(XRK_ATTR_CPU5_USE, iUse);
			break;
		case 7:
			MtReport_Attr_Set(XRK_ATTR_CPU6_USE, iUse);
			break;
		case 8:
			MtReport_Attr_Set(XRK_ATTR_CPU7_USE, iUse);
			break;
		case 9:
			MtReport_Attr_Set(XRK_ATTR_CPU8_USE, iUse);
			break;
		case 10:
			MtReport_Attr_Set(XRK_ATTR_CPU9_USE, iUse);
			break;
		case 11:
			MtReport_Attr_Set(XRK_ATTR_CPU10_USE, iUse);
			break;
		case 12:
			MtReport_Attr_Set(XRK_ATTR_CPU11_USE, iUse);
			break;
		case 13:
			MtReport_Attr_Set(XRK_ATTR_CPU12_USE, iUse);
			break;
		case 14:
			MtReport_Attr_Set(XRK_ATTR_CPU13_USE, iUse);
			break;
		case 15:
			MtReport_Attr_Set(XRK_ATTR_CPU14_USE, iUse);
			break;
		case 16:
			MtReport_Attr_Set(XRK_ATTR_CPU15_USE, iUse);
			break;

		default:
			MtReport_Log_Warn("not support cpu:%d, use:%d%%", iCpu, iUse);
	}
}

static void CalcuCpuAvgUse()
{
    if(g_stCpuUse.bStaticTimes <= 0) {
        MtReport_Log_Warn("static cpu times invalid (%u)", g_stCpuUse.bStaticTimes);
        return;
    }

    int iTotalUse = 0, iTmp = 0;
#define GET_CPU_AVG_USE(f) do { \
    iTmp = g_stCpuUse.f / g_stCpuUse.bStaticTimes; \
    iTmp /= 10; \
    if((iTmp == 0 && (g_stCpuUse.f%10) > 0) || (iTmp%10) > 0) \
        iTmp++; \
    g_stCpuUse.f = iTmp; \
    iTotalUse += g_stCpuUse.f; \
}while(0)

    GET_CPU_AVG_USE(iCpuUser);
    GET_CPU_AVG_USE(iCpuNice);
    GET_CPU_AVG_USE(iCpuSys);
    GET_CPU_AVG_USE(iCpuIoWa);
    GET_CPU_AVG_USE(iCpuIrq);
    GET_CPU_AVG_USE(iCpuSoftIrq);
    GET_CPU_AVG_USE(iCpuSteal);
    GET_CPU_AVG_USE(iCpuGuest);
    GET_CPU_AVG_USE(iCpuGuestNice);

    // idle 的计算使用排除其他项使用率的值
    // 注意cpu 整合的使用率 iTotalUse 的值因为取整的原因可能不等于 iCpuUse[0]
    g_stCpuUse.iCpuIdle=100-iTotalUse;

    for(int i=0; i < g_stCpuUse.iCpuCount; i++) {
        GET_CPU_AVG_USE(iCpuUse[i]);
    }

#undef GET_CPU_AVG_USE
}

static void ClearCpuAvgUse()
{
    g_stCpuUse.bStaticTimes = 0;

    g_stCpuUse.iCpuUser = 0;
    g_stCpuUse.iCpuNice = 0;
    g_stCpuUse.iCpuSys = 0;
    g_stCpuUse.iCpuIdle = 0;
    g_stCpuUse.iCpuIoWa = 0;
    g_stCpuUse.iCpuIrq = 0;
    g_stCpuUse.iCpuSoftIrq = 0;
    g_stCpuUse.iCpuSteal = 0;
    g_stCpuUse.iCpuGuest = 0;
    g_stCpuUse.iCpuGuestNice = 0;

    for(int i=0; i < g_stCpuUse.iCpuCount; i++) {
        g_stCpuUse.iCpuUse[i] = 0;
    }
    g_stCpuUse.dwProcessNew = 0;
    g_stCpuUse.dwCtxt = 0;
}

static void WriteCpuUse()
{
    if(g_stCpuUse.iCpuCount <= 0)
        return;

    CalcuCpuAvgUse();

    MtReport_Attr_Set(XRK_CPU_TOTAL_USER, g_stCpuUse.iCpuUser);
    MtReport_Attr_Set(XRK_CPU_TOTAL_NICE, g_stCpuUse.iCpuNice);
    MtReport_Attr_Set(XRK_CPU_TOTAL_SYS, g_stCpuUse.iCpuSys);
    MtReport_Attr_Set(XRK_CPU_TOTAL_IDLE, g_stCpuUse.iCpuIdle);
    MtReport_Attr_Set(XRK_CPU_TOTAL_IOWA, g_stCpuUse.iCpuIoWa);
    MtReport_Attr_Set(XRK_CPU_TOTAL_IRQ, g_stCpuUse.iCpuIrq);
    MtReport_Attr_Set(XRK_CPU_TOTAL_SOFTIRQ, g_stCpuUse.iCpuSoftIrq);
    MtReport_Attr_Set(XRK_CPU_TOTAL_STEAL, g_stCpuUse.iCpuSteal);
    MtReport_Attr_Set(XRK_CPU_TOTAL_GUEST, g_stCpuUse.iCpuGuest);
    MtReport_Attr_Set(XRK_CPU_TOTAL_GUESTNI, g_stCpuUse.iCpuGuestNice);

    MtReport_Attr_Add(XRK_TOTAL_CPU_CTXT, g_stCpuUse.dwCtxt);
    MtReport_Attr_Add(XRK_PROC_CREATE_COUNT, g_stCpuUse.dwProcessNew);

    MtReport_Log_Info("times:%d, cpu total - user:%d, nice:%d, sys:%d, idle:%d, iowa:%d, irq:%d, softirq:%d, "
        "steal:%d, guest:%d, guestni:%d, new process:%u, ctxt:%u",
        g_stCpuUse.bStaticTimes, g_stCpuUse.iCpuUser, g_stCpuUse.iCpuNice, g_stCpuUse.iCpuSys, g_stCpuUse.iCpuIdle, 
        g_stCpuUse.iCpuIoWa, g_stCpuUse.iCpuIrq, g_stCpuUse.iCpuSoftIrq, g_stCpuUse.iCpuSteal, g_stCpuUse.iCpuGuest,
        g_stCpuUse.iCpuGuestNice, g_stCpuUse.dwProcessNew, g_stCpuUse.dwCtxt);

    for(int i=0; i < g_stCpuUse.iCpuCount; i++) {
		ReportCpuUse(i, g_stCpuUse.iCpuUse[i]);
    }

    ClearCpuAvgUse();
    WriteCpuLoadAvg();
}

void WriteTcpUdpInfo()
{
    if(g_bNotSupportTcpUdp)
        return;

    MtReport_Attr_Set(XRK_TCP_CONN_COUNT, g_stNetUse.iCurTcpEstab);
    MtReport_Attr_Add(XRK_TCP_IN_PACKS, g_stNetUse.dwTcpPackIn);
    MtReport_Attr_Add(XRK_TCP_OUT_PACKS, g_stNetUse.dwTcpPackOut);
    MtReport_Attr_Add(XRK_UDP_IN_PACKS, g_stNetUse.dwUdpPackIn);
    MtReport_Attr_Add(XRK_UDP_OUT_PACKS, g_stNetUse.dwUdpPackOut);
    MtReport_Attr_Add(XRK_TCP_PACKS_ERROR, g_stNetUse.iTcpErr);
    MtReport_Attr_Add(XRK_UDP_PACKS_ERROR, g_stNetUse.iUdpErr);

    MtReport_Log_Info("tcp/udp info - conn:%d, udp in:%u, udp out:%u, tcp in:%u, tcp out:%u, udp error:%d, tcp error:%d",
        g_stNetUse.iCurTcpEstab, g_stNetUse.dwTcpPackIn, g_stNetUse.dwTcpPackOut, 
        g_stNetUse.dwUdpPackIn, g_stNetUse.dwUdpPackOut, g_stNetUse.iUdpErr, g_stNetUse.iTcpErr);
    memset(&g_stNetUse, 0, sizeof(g_stNetUse));
}

void ReadNetUse()
{
    TTcpUdpInfo stInfo;
    if(g_bNotSupportTcpUdp || GetNetTcpUdpInfo(&stInfo) < 0)
        return;

    g_stNetUse.iCurTcpEstab = stInfo.iCurTcpEstab;
    if(stInfo.dwUdpPackIn > g_stNetTcpUdpInfo.dwUdpPackIn)
        g_stNetUse.dwUdpPackIn += stInfo.dwUdpPackIn - g_stNetTcpUdpInfo.dwUdpPackIn;
    if(stInfo.dwUdpPackOut > g_stNetTcpUdpInfo.dwUdpPackOut)
        g_stNetUse.dwUdpPackOut += stInfo.dwUdpPackOut - g_stNetTcpUdpInfo.dwUdpPackOut;
    if(stInfo.dwTcpPackIn > g_stNetTcpUdpInfo.dwTcpPackIn)
        g_stNetUse.dwTcpPackIn += stInfo.dwTcpPackIn - g_stNetTcpUdpInfo.dwTcpPackIn;
    if(stInfo.dwTcpPackOut > g_stNetTcpUdpInfo.dwTcpPackOut)
        g_stNetUse.dwTcpPackOut += stInfo.dwTcpPackOut - g_stNetTcpUdpInfo.dwTcpPackOut;

    // tcp/udp 错误包监控, 上报日志
    if(g_iRemoteUdpPackErrorWarnLog && stInfo.dwUdpInErr > g_stNetTcpUdpInfo.dwUdpInErr) {
        MtReport_Log_Warn("udp in packet error: %u", stInfo.dwUdpInErr-g_stNetTcpUdpInfo.dwUdpInErr);
        g_stNetUse.iUdpErr += stInfo.dwUdpInErr-g_stNetTcpUdpInfo.dwUdpInErr;
    }
    if(g_iRemoteUdpPackErrorWarnLog && stInfo.dwUdpNoPorts > g_stNetTcpUdpInfo.dwUdpNoPorts) {
        MtReport_Log_Warn("udp in packet no ports error: %u", stInfo.dwUdpNoPorts-g_stNetTcpUdpInfo.dwUdpNoPorts);
        g_stNetUse.iUdpErr += stInfo.dwUdpNoPorts-g_stNetTcpUdpInfo.dwUdpNoPorts;
    }
    if(g_iLocalUdpPackErrorWarnLog && stInfo.dwUdpRecvBufErr > g_stNetTcpUdpInfo.dwUdpRecvBufErr) {
        MtReport_Log_Warn("udp recvbuf error: %u", stInfo.dwUdpRecvBufErr-g_stNetTcpUdpInfo.dwUdpRecvBufErr);
        g_stNetUse.iUdpErr += stInfo.dwUdpRecvBufErr-g_stNetTcpUdpInfo.dwUdpRecvBufErr;
    }
    if(g_iLocalUdpPackErrorWarnLog && stInfo.dwUdpSendBufErr > g_stNetTcpUdpInfo.dwUdpSendBufErr) {
        MtReport_Log_Warn("udp sendbuf error: %u", stInfo.dwUdpSendBufErr-g_stNetTcpUdpInfo.dwUdpSendBufErr);
        g_stNetUse.iUdpErr += stInfo.dwUdpSendBufErr-g_stNetTcpUdpInfo.dwUdpSendBufErr;
    }
    if(g_iRemoteTcpPackErrorWarnLog && stInfo.dwTcpReSends > g_stNetTcpUdpInfo.dwTcpReSends) {
        MtReport_Log_Warn("tcp resend packets error: %u", stInfo.dwTcpReSends-g_stNetTcpUdpInfo.dwTcpReSends);
        g_stNetUse.iTcpErr += stInfo.dwTcpReSends-g_stNetTcpUdpInfo.dwTcpReSends;
    }
    if(g_iRemoteTcpPackErrorWarnLog && stInfo.dwTcpInErr > g_stNetTcpUdpInfo.dwTcpInErr) {
        MtReport_Log_Warn("tcp in packets error: %u", stInfo.dwTcpInErr-g_stNetTcpUdpInfo.dwTcpInErr);
        g_stNetUse.iTcpErr += stInfo.dwTcpInErr-g_stNetTcpUdpInfo.dwTcpInErr;
    }
    if(g_iLocalTcpPackErrorWarnLog && stInfo.dwTcpOutRsts > g_stNetTcpUdpInfo.dwTcpOutRsts) {
        MtReport_Log_Warn("tcp out packets error: %u", stInfo.dwTcpOutRsts-g_stNetTcpUdpInfo.dwTcpOutRsts);
        g_stNetUse.iTcpErr += stInfo.dwTcpOutRsts-g_stNetTcpUdpInfo.dwTcpOutRsts;
    }
    memcpy(&g_stNetTcpUdpInfo, &stInfo, sizeof(stInfo));
}
 
static void ReadCpuUse()
{
    static TcpuUse stCpuUse;
    if(g_stCpuUse.iCpuCount <= 0)
        return;

    memset(&stCpuUse, 0, sizeof(stCpuUse));
    if(GetCpuUse(&stCpuUse) <= 0)
    {
        MtReport_Log_Warn("GetCpuUse failed !");
        return;      
    }
    g_stBaseInfo.iCpuUsePercent = stCpuUse.iCpuUse[0]/10;
    if(stCpuUse.iCpuUse[0]%10)
        g_stBaseInfo.iCpuUsePercent++;

    for(int i=0; i < g_stCpuUse.iCpuCount; i++)
        g_stCpuUse.iCpuUse[i] += stCpuUse.iCpuUse[i];
    g_stCpuUse.iCpuUser += stCpuUse.iCpuUser;
    g_stCpuUse.iCpuSys += stCpuUse.iCpuSys;
    g_stCpuUse.iCpuIdle += stCpuUse.iCpuIdle;
    g_stCpuUse.iCpuIoWa += stCpuUse.iCpuIoWa;
    g_stCpuUse.iCpuIrq += stCpuUse.iCpuIrq ;
    g_stCpuUse.iCpuSoftIrq += stCpuUse.iCpuSoftIrq;
    g_stCpuUse.dwCtxt += stCpuUse.dwCtxt;
    g_stCpuUse.dwProcessNew += stCpuUse.dwProcessNew;
    g_stCpuUse.bStaticTimes ++;
}

static void WriteMemUse()
{   
    if(g_stMemUse.bStaticTimes > 0) {
        g_stMemUse.iMemUse /= g_stMemUse.bStaticTimes;
        g_stMemUse.iSwapUse /= g_stMemUse.bStaticTimes;

		MtReport_Attr_Set(XRK_MEM_USE, g_stMemUse.iMemUse);
		MtReport_Attr_Set(XRK_SWAP_MEM_USE, g_stMemUse.iSwapUse);
        MtReport_Attr_Set(XRK_MEM_AVAILABLE, g_stMemUse.dwAvailable);
		MtReport_Log_Info("report mem info, use:%d, swap use:%d, available:%ukB", 
            g_stMemUse.iMemUse, g_stMemUse.iSwapUse, g_stMemUse.dwAvailable);
        memset(&g_stMemUse, 0, sizeof(g_stMemUse));
	}
}

static TDiskInfo *GetDiskInfo(const char *pname)
{
    std::map<std::string, TDiskInfo>::iterator it = g_stDiskInfo.find(pname);
    if(it != g_stDiskInfo.end())
        return (TDiskInfo*)&(it->second);
    return NULL;
}

static void WriteDiskIoStat()
{
    if(!g_pDiskStatAry || !g_pDiskStatAryForTable)
        return;

    static TDiskStatInfo * s_pDiskStatAry = NULL;
    if(!s_pDiskStatAry) {
        s_pDiskStatAry = new TDiskStatInfo[g_iMaxDiskStatCount];
        if(!s_pDiskStatAry) {
            MtReport_Log_Error("new TDiskStatInfo failed, count:%d, msg:%s", g_iMaxDiskStatCount, strerror(errno));
            return;
        }
    }
    static uint32_t s_dwLastWriteTableDiskInfoTime = g_dwCurTimeSec;

    int iDiskStatCount = 0;
    if((iDiskStatCount=ReadDiskStatInfo(s_pDiskStatAry, g_iMaxDiskStatCount)) < 0)
        return;
    if(iDiskStatCount != g_iDiskStatCount) {
        MtReport_Log_Warn("disk stat count changed (%d, %d)", iDiskStatCount, g_iDiskStatCount);
        memcpy(g_pDiskStatAry, s_pDiskStatAry, iDiskStatCount*sizeof(*s_pDiskStatAry));
        g_iDiskStatCount = iDiskStatCount;
        return;
    }
    
    uint32_t dwReadTimes = 0;
    uint32_t dwMergeReadTimes = 0;
    uint32_t dwReadSectors = 0;
    uint32_t dwReadTimeMs = 0;
    uint32_t dwWriteTimes = 0;
    uint32_t dwMergeWriteTimes = 0;
    uint32_t dwWriteSectors = 0;
    uint32_t dwWriteTimeMs = 0;
    uint32_t dwIOTimeMs = 0;
    uint32_t dwIOTotalTimeMs = 0;

    for(int i=0, len=0; i < iDiskStatCount; i++) {
        if(g_iDiskReadTimesWarnCfg > 0
            && s_pDiskStatAry[i].dwReadTimes > g_pDiskStatAry[i].dwReadTimes+g_iDiskReadTimesWarnCfg) {
            MtReport_Log_Warn("disk:%s read times:%u over:%d", s_pDiskStatAry[i].szDevName,
                s_pDiskStatAry[i].dwReadTimes-g_pDiskStatAry[i].dwReadTimes, g_iDiskReadTimesWarnCfg);
        }
        if(g_iDiskWriteTimesWarnCfg > 0
            && s_pDiskStatAry[i].dwWriteTimes > g_pDiskStatAry[i].dwWriteTimes+g_iDiskWriteTimesWarnCfg) {
            MtReport_Log_Warn("disk:%s writes times:%u over:%d", s_pDiskStatAry[i].szDevName,
                s_pDiskStatAry[i].dwWriteTimes-g_pDiskStatAry[i].dwWriteTimes, g_iDiskWriteTimesWarnCfg);
        }
        if(g_iReadSectorsWarnCfg > 0
            && s_pDiskStatAry[i].dwReadSectors > g_pDiskStatAry[i].dwReadSectors+g_iReadSectorsWarnCfg) {
            MtReport_Log_Warn("disk:%s read sectors:%u over:%d", s_pDiskStatAry[i].szDevName,
                s_pDiskStatAry[i].dwReadSectors-g_pDiskStatAry[i].dwReadSectors, g_iReadSectorsWarnCfg);
        }
        if(g_iWriteSectorsWarnCfg > 0
            && s_pDiskStatAry[i].dwWriteSectors > g_pDiskStatAry[i].dwWriteSectors+g_iWriteSectorsWarnCfg) {
            MtReport_Log_Warn("disk:%s writes sectors:%u over:%d", s_pDiskStatAry[i].szDevName,
                s_pDiskStatAry[i].dwWriteSectors-g_pDiskStatAry[i].dwWriteSectors, g_iWriteSectorsWarnCfg);
        }
        if(g_iIoUseTimeMsWarnCfg > 0
            && s_pDiskStatAry[i].dwIOTotalTimeMs > g_pDiskStatAry[i].dwIOTotalTimeMs+g_iIoUseTimeMsWarnCfg) {
            MtReport_Log_Warn("disk:%s io use time:%u over:%d", s_pDiskStatAry[i].szDevName,
                s_pDiskStatAry[i].dwIOTotalTimeMs-g_pDiskStatAry[i].dwIOTotalTimeMs, g_iIoUseTimeMsWarnCfg);
        }

        len = strlen(s_pDiskStatAry[i].szDevName);
        if(isdigit(s_pDiskStatAry[i].szDevName[len-1]))
            continue;
#define DISK_STAT_STATICS(field) do { \
            if(s_pDiskStatAry[i].field > g_pDiskStatAry[i].field) \
                field += s_pDiskStatAry[i].field - g_pDiskStatAry[i].field; \
        }while(0)
        DISK_STAT_STATICS(dwReadTimes);
        DISK_STAT_STATICS(dwMergeReadTimes);
        DISK_STAT_STATICS(dwReadSectors);
        DISK_STAT_STATICS(dwReadTimeMs);
        DISK_STAT_STATICS(dwWriteTimes);
        DISK_STAT_STATICS(dwMergeWriteTimes);
        DISK_STAT_STATICS(dwWriteSectors);
        DISK_STAT_STATICS(dwWriteTimeMs);
        DISK_STAT_STATICS(dwIOTimeMs);
        DISK_STAT_STATICS(dwIOTotalTimeMs);
#undef DISK_STAT_STATICS
    }

    MtReport_Attr_Add(XRK_DISK_R_TIMES, dwReadTimes);
    MtReport_Attr_Add(XRK_DISK_MERGE_R_TIMES, dwMergeReadTimes);
    MtReport_Attr_Add(XRK_DISK_R_SECTORS, dwReadSectors);
    MtReport_Attr_Add(XRK_DISK_R_USE_MS, dwReadTimeMs);
    MtReport_Attr_Add(XRK_DISK_W_TIMES, dwWriteTimes);
    MtReport_Attr_Add(XRK_DISK_MERGE_W_TIMES, dwMergeWriteTimes);
    MtReport_Attr_Add(XRK_DISK_W_SECTORS, dwWriteSectors);
    MtReport_Attr_Add(XRK_DISK_W_USE_MS, dwWriteTimeMs);
    MtReport_Attr_Add(XRK_DISK_IO_USE_MS, dwIOTotalTimeMs);
    memcpy(g_pDiskStatAry, s_pDiskStatAry, iDiskStatCount*sizeof(*s_pDiskStatAry));

    char sMountBuf[512] = {0};
    if(g_dwCurTimeSec >= s_dwLastWriteTableDiskInfoTime+g_iTableDiskStatAllStaticTime) {
        for(int i=0, len=0; i < iDiskStatCount; i++) {
            len = strlen(s_pDiskStatAry[i].szDevName);
            if(!isdigit(s_pDiskStatAry[i].szDevName[len-1]))
                continue;
#define DISK_STAT_STATICS_FOR_TABLE(field) do { \
            if(s_pDiskStatAry[i].field > g_pDiskStatAryForTable[i].field) \
                field = s_pDiskStatAry[i].field - g_pDiskStatAryForTable[i].field; \
            else \
                field = 0; \
        }while(0)
            DISK_STAT_STATICS_FOR_TABLE(dwReadTimes);
            DISK_STAT_STATICS_FOR_TABLE(dwMergeReadTimes);
            DISK_STAT_STATICS_FOR_TABLE(dwReadSectors);
            DISK_STAT_STATICS_FOR_TABLE(dwReadTimeMs);
            DISK_STAT_STATICS_FOR_TABLE(dwWriteTimes);
            DISK_STAT_STATICS_FOR_TABLE(dwMergeWriteTimes);
            DISK_STAT_STATICS_FOR_TABLE(dwWriteSectors);
            DISK_STAT_STATICS_FOR_TABLE(dwWriteTimeMs);
            DISK_STAT_STATICS_FOR_TABLE(dwIOTimeMs);
            DISK_STAT_STATICS_FOR_TABLE(dwIOTotalTimeMs);

            TDiskInfo *pdiskInfo = GetDiskInfo(s_pDiskStatAry[i].szDevName);
            if(pdiskInfo)
                strncpy(sMountBuf, pdiskInfo->strMount.c_str(), sizeof(sMountBuf)-1);
            else
                strcpy(sMountBuf, "null");
            int iRet = MtReport_Plugin_Table(
                XRK_PLUGIN_ID, XRK_TABLE_DISK_STAT_ALL, g_iTableDiskStatAllStaticTime, g_dwCurTimeSec,
                "%d#%s$%d#%s$%d#%lu$%d#%u$%d#%u$%d#%u$%d#%u$%d#%u$%d#%u$%d#%u$%d#%u",
                XRK_FLD_DISK_PART_NAME, XRK_PLUGIN_TABLE_STR_CHANGE(s_pDiskStatAry[i].szDevName),
                XRK_FLD_DISK_MOUNT, XRK_PLUGIN_TABLE_STR_CHANGE(sMountBuf),
                XRK_FLD_DISK_SPACE, (pdiskInfo ? pdiskInfo->qwSizeK: 0),
                XRK_FLD_DISK_R_TIMES, dwReadTimes,
                XRK_FLD_DISK_W_TIMES, dwWriteTimes,
                XRK_FLD_DISK_R_SECTORS, dwReadSectors,
                XRK_FLD_DISK_W_SECTORS, dwWriteSectors,
                XRK_FLD_DISK_R_USE_MS, dwReadTimeMs,
                XRK_FLD_DISK_W_USE_MS, dwWriteTimeMs,
                XRK_FLD_DISK_IO_USE_MS, dwIOTotalTimeMs,
                XRK_FLD_DISK_USE_PERCENT, 
                    (pdiskInfo ? (uint32_t)ceil(pdiskInfo->qwUsed*100.0/(pdiskInfo->qwUsed+pdiskInfo->qwRemain)) : 0));
            if(iRet != 0)
                MtReport_Log_Error("report realtime table(%d) failed, ret:%d", XRK_TABLE_NET_DEV_ALL, iRet);
#undef DISK_STAT_STATICS_FOR_TABLE_FOR_TABLE 
        }

        s_dwLastWriteTableDiskInfoTime = g_dwCurTimeSec;
        memcpy(g_pDiskStatAryForTable, s_pDiskStatAry, iDiskStatCount*sizeof(*s_pDiskStatAry));
    }

    MtReport_Log_Info("disk io stat: read:%u, write:%u, merge read:%u, mrege write:%u, read sector:%u, write sector:%u"
        ", read use ms:%u, write use ms:%u, io total use:(%u, %u)",
        dwReadTimes, dwWriteTimes, dwMergeReadTimes, dwMergeWriteTimes, dwReadSectors, dwWriteSectors,
        dwReadTimeMs, dwWriteTimeMs, dwIOTimeMs, dwIOTotalTimeMs);
}

static void WriteDiskUse()
{   
    uint64_t qwTotalSpace = 0, qwTotalUse = 0;
    uint32_t maxUsePer = 0;
    if(GetDiskInfo(qwTotalSpace, qwTotalUse, maxUsePer) < 0)
        return;
    uint32_t dwUsePerTotal = (uint32_t)ceil((qwTotalUse*100.0/qwTotalSpace));

    g_stBaseInfo.dwDiskTotalSpace = qwTotalSpace;
    g_stBaseInfo.iDiskUsePercent = dwUsePerTotal;

    MtReport_Attr_Set(XRK_DISK_TOTAL_USE, dwUsePerTotal);
    MtReport_Attr_Set(XRK_DISK_USE_MAX, maxUsePer);
    if(maxUsePer > 95) 
        MtReport_Attr_Add(XRK_DISK_USE_OVER_95, 1);
    else if(maxUsePer > 90)
        MtReport_Attr_Add(XRK_DISK_USE_OVER_90, 1);
    else if(maxUsePer > 80)
        MtReport_Attr_Add(XRK_DISK_USE_OVER_80, 1);
    else if(maxUsePer > 70)
        MtReport_Attr_Add(XRK_DISK_USE_OVER_70, 1);
    MtReport_Log_Info("get disk use total percent:%u%%, max use percent:%u%%", dwUsePerTotal, maxUsePer);
}

int InitAttrConfig(std::string &conf)
{
	char *pattrs = strdup(XRK_PLUGIN_ALL_ATTRS);
	if(pattrs == NULL)
		return ERROR_LINE;
	char *pmem = pattrs;
	char *pattr = NULL, *psave = NULL;
	const char *pfirst = NULL, *plast = NULL;

	int i = 0, iAttrId = 0;
	for(; i < XRK_PLUGIN_ATTRS_COUNT_MAX; i++)
	{
		pattr = strtok_r(pattrs, " ", &psave);
		if(NULL == pattr) {
			i++;
			break;
		}

		if(LoadConfig(conf.c_str(), pattr, CFG_INT, &iAttrId, 0, (void*)NULL) < 0) { 
			MtReport_Log_Error("read attr:%s from file:%s failed", pattr, conf.c_str());
			break;
		}
		g_aryPluginAttr[i] = iAttrId;
		MtReport_Log_Debug("read attr config:%s, attr id:%d, ary index:%d", pattr, iAttrId, i);
		if(i==0)
			pfirst = pattr;
		else if(i+1 == XRK_PLUGIN_ATTRS_COUNT_MAX)
			plast = pattr;
		pattrs = NULL;
	}
	if(i < XRK_PLUGIN_ATTRS_COUNT_MAX) {
		MtReport_Log_Error("read attr failed (%d, %d), pattr:%p", i, XRK_PLUGIN_ATTRS_COUNT_MAX, pattr);
		free(pmem);
		return ERROR_LINE;
	}
	MtReport_Log_Info("read attr count:%d, first:(%s,%d), last:(%s,%d)",
		i, pfirst, g_aryPluginAttr[0], plast, g_aryPluginAttr[i-1]);
	free(pmem);
	return 0;
}

// 插件初始化函数
int SlogPlusInit()
{
	// 读取配置文件
	std::string strPlugConfFile("./xrk_");
	strPlugConfFile += g_strPluginName;
	strPlugConfFile += ".conf";
	int iRet = 0, iPluginId = 0;
    char szVersion[32] = {0};
	if((iRet=LoadConfig(strPlugConfFile.c_str(),
        "XRK_PLUGIN_CONFIG_FILE_VER", CFG_STRING, szVersion, "", sizeof(szVersion),
		"READ_BASEINFO_PER_TIME_SEC", CFG_INT, &g_iReadInfoPerTimeSec, 5,
		"REPORT_BASEINFO_PER_TIME_SEC", CFG_INT, &g_iReportInfoPerTimeSec, 20,
        "NOT_MONITOR_NET_LO", CFG_INT, &g_iNotMonitorNetLo, 0,
        "MAX_DISKSTAT_COUNT", CFG_INT, &g_iMaxDiskStatCount, 500,
        "XRK_PLUGIN_ID", CFG_INT, &iPluginId, 0,
        "DISK_IO_READ_TIMES_WARN", CFG_INT, &g_iDiskReadTimesWarnCfg, 0,
        "DISK_IO_WRITE_TIMES_WARN", CFG_INT, &g_iDiskWriteTimesWarnCfg, 0,
        "DISK_IO_READ_SECTORS_WARN", CFG_INT, &g_iReadSectorsWarnCfg, 0,
        "DISK_IO_WRITE_SECTORS_WARN", CFG_INT, &g_iWriteSectorsWarnCfg, 0,
        "TCP_PACK_LOCAL_ERROR_WARN", CFG_INT, &g_iLocalTcpPackErrorWarnLog, 1,
        "UDP_PACK_LOCAL_ERROR_WARN", CFG_INT, &g_iLocalUdpPackErrorWarnLog, 1,
        "TCP_PACK_REMOTE_ERROR_WARN", CFG_INT, &g_iRemoteTcpPackErrorWarnLog, 0,
        "UDP_PACK_REMOTE_ERROR_WARN", CFG_INT, &g_iRemoteUdpPackErrorWarnLog, 0,
        "XRK_TABLE_BASE_INFO_STATIC_TIME", CFG_INT, &g_iTableBaseInfoStaticTime, XRK_TABLE_BASE_INFO_DEF_STAT_TIME,
        "XRK_TABLE_DISK_STAT_ALL_STATIC_TIME", CFG_INT, &g_iTableDiskStatAllStaticTime, XRK_TABLE_DISK_STAT_ALL_DEF_STAT_TIME,
        "XRK_TABLE_NET_DEV_ALL_STATIC_TIME", CFG_INT, &g_iTableNetDevAllStaticTime, XRK_TABLE_NET_DEV_ALL_DEF_STAT_TIME,
        "XRK_TABLE_NET_TU_PACK_STAT_STATIC_TIME", CFG_INT, &g_iTableNetTuPackStatStaticTime, XRK_TABLE_NET_TU_PACK_STAT_DEF_STAT_TIME,
        "XRK_TABLE_CPU_ALL_STAT_STATIC_TIME", CFG_INT, &g_iTableCpuAllStatStaticTime, XRK_TABLE_CPU_ALL_STAT_DEF_STAT_TIME,
        "XRK_TABLE_MEM_STAT_STATIC_TIME", CFG_INT, &g_iTableMemStatStaticTime, XRK_TABLE_MEM_STAT_DEF_STAT_TIME,
		(void*)NULL)) < 0)
	{
		fprintf(stderr, "loadconfig from:%s failed, msg:%s !\n", strPlugConfFile.c_str(), strerror(errno));
		return ERROR_LINE;
	}

#define CHECK_TABLE_STATIC_TIME(table_time) \
    if(table_time < 10) \
        table_time = 10; \
    else if(table_time > 86400) \
        table_time = 86400; \
    if(g_iReadInfoPerTimeSec > table_time) \
        g_iReadInfoPerTimeSec = table_time; \
    if(g_iReportInfoPerTimeSec > table_time) \
        g_iReportInfoPerTimeSec = table_time;
    CHECK_TABLE_STATIC_TIME(g_iTableBaseInfoStaticTime);
    CHECK_TABLE_STATIC_TIME(g_iTableDiskStatAllStaticTime);
    CHECK_TABLE_STATIC_TIME(g_iTableNetDevAllStaticTime);
    CHECK_TABLE_STATIC_TIME(g_iTableNetTuPackStatStaticTime);
    CHECK_TABLE_STATIC_TIME(g_iTableCpuAllStatStaticTime);
    CHECK_TABLE_STATIC_TIME(g_iTableMemStatStaticTime);
#undef CHECK_TABLE_STATIC_TIME

    if(g_iReadInfoPerTimeSec < 1)
        g_iReadInfoPerTimeSec = 1;
    else if(g_iReadInfoPerTimeSec > 30)
        g_iReadInfoPerTimeSec = 30;
    if(g_iReportInfoPerTimeSec < 10)
        g_iReportInfoPerTimeSec = 10;
    else if(g_iReportInfoPerTimeSec > 50)
        g_iReportInfoPerTimeSec = 50;
    if(g_iReportInfoPerTimeSec <= g_iReadInfoPerTimeSec)
        g_iReportInfoPerTimeSec = g_iReadInfoPerTimeSec+1;

    if(g_iMaxDiskStatCount < 10)
        g_iMaxDiskStatCount = 10;
    else if(g_iMaxDiskStatCount > 1024)
        g_iMaxDiskStatCount = 1024;

	iRet = MtReport_Plus_Init(
        strPlugConfFile.c_str(), iPluginId, XRK_PLUGIN_NAME, XRK_PLUGIN_HEADER_FILE_VER);
	if(iRet < 0 || g_mtReport.pMtShm == NULL) {
		return ERROR_LINE;
	}      

    // 从这里开始可以使用库的相关接口了
	if((iRet=InitAttrConfig(strPlugConfFile)) < 0) 
	{
		fprintf(stderr, "read plugin attr info failed, ret:%d\n", iRet);
		MtReport_Log_Error("read plugin attr info failed:%d", iRet);
		return ERROR_LINE;
	}

    // 版本号检查, 配置文件版本号需要大于等于编译版本号，即插件配置向后兼容
    // 配置文件的版本号不断增长，但可执行文件版本号变化较少
    if(!IsPluginVersionOk(szVersion, XRK_PLUGIN_HEADER_FILE_VER)) {
        MtReport_Log_Warn("check version failed, %s < %s", szVersion, XRK_PLUGIN_HEADER_FILE_VER);
    }
    memset(&g_stBaseInfo, 0, sizeof(g_stBaseInfo));

	// cpu 监控 -- start 
	if((iRet=InitGetCpuUse()) <= 0) {
		MtReport_Log_Error("InitGetCpuUse failed !");
        g_stCpuUse.iCpuCount = 0;
	}
    else {
        int iCount = MAX_CPU_SUPPORT+1;
        if(iCount > iRet)
            iCount = iRet;
        g_stCpuUse.iCpuCount = iCount;
    }

    // 网络
	InitGetNet();
    if(GetNetTcpUdpInfo(&g_stNetTcpUdpInfo) < 0)
        g_bNotSupportTcpUdp = true;
    else
        g_bNotSupportTcpUdp = false;

    // 磁盘
    g_pDiskStatAry = new TDiskStatInfo[g_iMaxDiskStatCount];
    g_pDiskStatAryForTable = new TDiskStatInfo[g_iMaxDiskStatCount];
    if(!g_pDiskStatAry || !g_pDiskStatAryForTable) {
	    MtReport_Log_Error("new TDiskStatInfo failed, count:%d, msg:%s", g_iMaxDiskStatCount, strerror(errno));
        return -1;
    }
    if((g_iDiskStatCount=InitReadDiskStatInfo(g_pDiskStatAry, g_iMaxDiskStatCount)) < 0) {
        delete [] g_pDiskStatAry;
        g_pDiskStatAry = NULL;
    }
    memcpy(g_pDiskStatAryForTable, g_pDiskStatAry, g_iDiskStatCount*sizeof(TDiskStatInfo));
    GetCpuInfo();

	MtReport_Log_Info("plugin:%s init ok, cpu count:%d, disk stat count:%d, cpu info:%s", 
        XRK_PLUGIN_NAME, g_stCpuUse.iCpuCount-1, g_iDiskStatCount, g_szCpuInfo);
	return 0;
}

// 插件逻辑实现函数，请勿在该函数中睡眠/阻塞
void SlogPlusOnLoop(uint32_t dwTimeNow)
{
	static uint32_t s_dwLastReadTime=dwTimeNow;
	static uint32_t s_dwLastWriteTime=dwTimeNow;

    // for table
	static uint32_t s_dwLastWriteTableBaseTime = dwTimeNow;

    // 实时表格
    if(dwTimeNow >= s_dwLastWriteTableBaseTime+g_iTableBaseInfoStaticTime)
    {
        int iRet = MtReport_Plugin_Table(
            XRK_PLUGIN_ID, XRK_TABLE_BASE_INFO, g_iTableBaseInfoStaticTime, g_dwCurTimeSec,
            "%d#%u$%d#%d$%d#%u$%d#%d$%d#%d$%d#%s$%d#%lu$%d#%lu",
            XRK_FLD_DISK_TOTAL_SPACE, g_stBaseInfo.dwDiskTotalSpace,
            XRK_FLD_DISK_USE, g_stBaseInfo.iDiskUsePercent,
            XRK_FLD_MEM_SIZE, g_stBaseInfo.dwMemSpace,
            XRK_FLD_MEM_USE, g_stBaseInfo.iMemUsePercent,
            XRK_FLD_CPU_USE, g_stBaseInfo.iCpuUsePercent,
            XRK_FLD_CPU_INFO, g_szCpuInfo,
            XRK_FLD_NET_T_SENDB, g_stBaseInfo.qwSendBytes/1024,
            XRK_FLD_NET_T_RECVB, g_stBaseInfo.qwRecvBytes/1024);
        if(iRet != 0)
            MtReport_Log_Error("report realtime table(%d) failed, ret:%d", XRK_TABLE_NET_DEV_ALL, iRet);
        g_stBaseInfo.qwSendBytes = 0;
        g_stBaseInfo.qwRecvBytes = 0;
        s_dwLastWriteTableBaseTime = dwTimeNow;
    }


	// 采集数据
	if(dwTimeNow >= s_dwLastReadTime+g_iReadInfoPerTimeSec)
	{
		ReadCpuUse();
		ReadNetUse();
		ReadMemUse();
		s_dwLastReadTime = dwTimeNow;
	}

	// 上报数据
	if(dwTimeNow >= s_dwLastWriteTime+g_iReportInfoPerTimeSec)
	{
        WriteCpuUse();
        ReportNetInfo();	
        WriteTcpUdpInfo();
        WriteDiskUse();
        WriteDiskIoStat();
        WriteMemUse();
		s_dwLastWriteTime = dwTimeNow;
	}

    int iRet = MtReport_Plus_Hello(dwTimeNow);
    if(iRet) {
        MtReport_Log_Error("plugin check failed, ret:%d", iRet);

        // 避免被 crontab 自动拉起
        system("./stop.sh > /dev/null 2>&1");
        s_exit = true;
    }
}

void deal_exist_sig(int sig)
{
    MtReport_Log_Info("plugin:%s, get exist signal:%d", XRK_PLUGIN_NAME, sig);
    s_exit = true;
}

void Deamon()
{
    daemon(1, 1);
    RegisterSignal(CORE_INFO_FILE, NULL, NULL);
    signal(SIGUSR1, deal_exist_sig);
}

int main(int argc, char *argv[])
{
	if(SlogPlusInit() < 0)
		return -1;

    Deamon();
    while(!s_exit) {
        g_dwCurTimeSec = time(NULL);
		SlogPlusOnLoop(g_dwCurTimeSec);
		usleep(1000+rand()%1000000);
	}

    if(g_pDiskStatAry) {
        delete [] g_pDiskStatAry;
        delete [] g_pDiskStatAryForTable;
    }
	return 0;
}

